using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Surface;
using Microsoft.Surface.Core;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;

namespace SurfaceNodeBattle
{
    /// <summary>
    /// This is the main type for your application.
    /// </summary>
    public class App1 : Microsoft.Xna.Framework.Game
    {
        private readonly GraphicsDeviceManager graphics;
        private ContactTarget contactTarget;
        private Color backgroundColor = new Color(0, 0, 0);
        private bool applicationLoadCompleteSignalled;

        private UserOrientation currentOrientation = UserOrientation.Bottom;
        private Matrix screenTransform = Matrix.Identity;
        private Matrix inverted;

        // application state: Activated, Previewed, Deactivated,
        // start in Activated state
        private bool isApplicationActivated = true;
        private bool isApplicationPreviewed;

        #region variables

        SpriteFont courierNew;

        private SpriteBatch battleSprite;
        private Texture2D node;
        private BasicEffect basicEffect;
        //private Texture2D car;
        //private Texture2D tagOrgiPosTexture;

        private float sizeOfNodeHitbox;

        private bool crashed = false;
        private float surfaceWidth;
        private float surfaceHeight;
        private int nrOfNodesThatShouldSpawn = 20;
        private long tropSpawnInterval = 500;
        private long nextTropSpawn = 500;
        Random rand = new Random();


        private List<Node> nodes = new List<Node>();

        private List<int> oldContacts = new List<int>();
        private Dictionary<int, Line> oldContactsStartStop = new Dictionary<int, Line>();
        private Dictionary<Node, List<Node>> nodeConnections = new Dictionary<Node, List<Node>>();
        private List<VertexPositionColor> lines = new List<VertexPositionColor>();
        /*private Dictionary<ByteTag, Vector2> firstTagPos = new Dictionary<ByteTag, Vector2>();
        private Dictionary<ByteTag, Car> tagCarRelation = new Dictionary<ByteTag, Car>();*/

        #endregion

        /// <summary>
        /// The graphics device manager for the application.
        /// </summary>
        protected GraphicsDeviceManager Graphics
        {
            get { return graphics; }
        }

        /// <summary>
        /// The target receiving all surface input for the application.
        /// </summary>
        protected ContactTarget ContactTarget
        {
            get { return contactTarget; }
        }

        /// <summary>
        /// Default constructor.
        /// </summary>
        public App1()
        {
            graphics = new GraphicsDeviceManager(this);
        }

        #region Initialization

        /// <summary>
        /// Moves and sizes the window to cover the input surface.
        /// </summary>
        private void SetWindowOnSurface()
        {
            System.Diagnostics.Debug.Assert(Window.Handle != System.IntPtr.Zero,
                "Window initialization must be complete before SetWindowOnSurface is called");
            if (Window.Handle == System.IntPtr.Zero)
                return;

            // We don't want to run in full-screen mode because we need
            // overlapped windows, so instead run in windowed mode
            // and resize to take up the whole surface with no border.

            // Make sure the graphics device has the correct back buffer size.
            InteractiveSurface interactiveSurface = InteractiveSurface.DefaultInteractiveSurface;
            if (interactiveSurface != null)
            {
                graphics.PreferredBackBufferWidth = interactiveSurface.Width;
                graphics.PreferredBackBufferHeight = interactiveSurface.Height;
                graphics.ApplyChanges();

                // Remove the border and position the window.
                Program.RemoveBorder(Window.Handle);
                Program.PositionWindow(Window);
            }
        }

        /// <summary>
        /// Initializes the surface input system. This should be called after any window
        /// initialization is done, and should only be called once.
        /// </summary>
        private void InitializeSurfaceInput()
        {
            System.Diagnostics.Debug.Assert(Window.Handle != System.IntPtr.Zero,
                "Window initialization must be complete before InitializeSurfaceInput is called");
            if (Window.Handle == System.IntPtr.Zero)
                return;
            System.Diagnostics.Debug.Assert(contactTarget == null,
                "Surface input already initialized");
            if (contactTarget != null)
                return;

            // Create a target for surface input.
            contactTarget = new ContactTarget(Window.Handle, EventThreadChoice.OnBackgroundThread);
            contactTarget.EnableInput();
        }

        /// <summary>
        /// Reset the application's orientation and transform based on the current launcher orientation.
        /// </summary>
        private void ResetOrientation()
        {
            UserOrientation newOrientation = ApplicationLauncher.Orientation;

            if (newOrientation == currentOrientation) { return; }

            currentOrientation = newOrientation;

            if (currentOrientation == UserOrientation.Top)
            {
                screenTransform = inverted;
            }
            else
            {
                screenTransform = Matrix.Identity;
            }
        }

        #endregion

        #region helperfunctions

        public void NodeSpawner(int nrThatShouldSpawn)
        {
            for (int i = 0; i < nrThatShouldSpawn; i++)
            {
                nodes.Add(new Node(new Vector2(rand.Next(100, 924), rand.Next(100, 668)), Color.White,rand.Next(5,20)));
                foreach (Node newNode in nodes)
                {
                    //OnTarget(newNode, nodes.Last(),sizeOfNodeHitbox * 2.5f) OLD
                    while (nodes.Find(delegate(Node n)
                    {
                        if (n == nodes.Last())
                            return false;
                        return OnTarget(n.Position, nodes.Last().Position, sizeOfNodeHitbox * 2.5f);
                    }) != null)
                    {
                        nodes.Last().Position = new Vector2(rand.Next(100, 924), rand.Next(100, 668));
                    }
                }
            }
        }

        private bool OnTarget(Vector2 plane, Vector2 target)
        {
            return OnTarget(plane, target, sizeOfNodeHitbox);
        }
        private bool OnTarget(Vector2 plane, Vector2 target, float hitDistance)
        {
            if (Vector2.Distance(plane, target) <= hitDistance)
            {
                return true;
            }
            return false;
        }

        private List<Node> problemSolver = new List<Node>();
        private void UpdateTroops(Node fromNode)
        {
            if (!(fromNode.Color == Color.White) && fromNode.NrOfTroops < 50)
            {
                fromNode.incTroops();
            }
            if (nodeConnections.ContainsKey(fromNode) && nodeConnections[fromNode].Count > 0)
            {
                fromNode.decTroops();
                int randReciver = rand.Next(nodeConnections[fromNode].Count);
                if (nodeConnections[fromNode][randReciver].Color != fromNode.Color)
                {
                    //nodeConnections[fromNode][randReciver].decTroops();
                    if (nodeConnections[fromNode][randReciver].NrOfTroops == 0)
                    {
                        if (nodeConnections[fromNode][randReciver].LastAttacker == fromNode.Color)
                        {
                            nodeConnections[fromNode][randReciver].Color = fromNode.Color;
                            nodeConnections.Remove(nodeConnections[fromNode][randReciver]);
                        }
                        else
                        {
                            nodeConnections[fromNode][randReciver].LastAttacker = fromNode.Color;
                        }
                    }
                    else if (nodeConnections[fromNode][randReciver].NrOfTroops > 0)
                    {
                        nodeConnections[fromNode][randReciver].decTroops();
                        nodeConnections[fromNode][randReciver].LastAttacker = fromNode.Color;
                    }
                    else if (nodeConnections[fromNode][randReciver].NrOfTroops < 0)
                    {
                        nodeConnections[fromNode][randReciver].Color = Color.Violet;
                        nodeConnections[fromNode][randReciver].NrOfTroops = 15;
                    }
                }
                else
                {
                    if (!problemSolver.Contains(nodeConnections[fromNode][randReciver]))
                    {
                        problemSolver.Add(fromNode);
                        UpdateTroops(nodeConnections[fromNode][randReciver]);
                        problemSolver.Remove(fromNode);
                    }
                }
            }
        }

        private void CheckForCrossedConnections(Line line, Node startConnection)
        {
            if (!nodeConnections.ContainsKey(startConnection) || nodeConnections[startConnection].Count == 0)
                return;
            line.CalculateLineExpression();
            foreach (Node endNode in nodeConnections[startConnection])
            {
                if (Vector3.Cross(new Vector3(endNode.Position - startConnection.Position, 0),
                    new Vector3(line.Stop - line.Start, 0)).Length() == 0)
                {
                    continue;
                }
                Line crossLine = new Line(startConnection.Position, endNode.Position);
                //Intersection xi = - (a1 - a2) / (b1 - b2)  ,  yi = a1 + b1xi 
                float xi = -(line.LineA - crossLine.LineA) / (line.LineB - crossLine.LineB);
                float yi = (line.LineA + crossLine.LineB * xi);
                // on line if (x1 - xi) (xi - x2) >= 0 and same for y , on crossline if (u1 - xi) (xi - u2) >= 0 and same for y 
                if (((line.Start.X - xi) * (xi - line.Stop.X) >= 0) && ((line.Start.Y - yi) * (yi - line.Stop.Y) <= 0) &&
                    ((crossLine.Start.X - xi) * (xi - crossLine.Stop.X) >= 0) && ((crossLine.Start.Y - yi) * (yi - crossLine.Stop.Y) <= 0))
                {
                    nodeConnections[startConnection].Remove(endNode);
                    break;
                }

            }
        }

        #endregion


        #region Overridden Game Methods

        /// <summary>
        /// Allows the app to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            SetWindowOnSurface();
            InitializeSurfaceInput();

            // Set the application's orientation based on the current launcher orientation
            currentOrientation = ApplicationLauncher.Orientation;

            // Subscribe to surface application activation events
            ApplicationLauncher.ApplicationActivated += OnApplicationActivated;
            ApplicationLauncher.ApplicationPreviewed += OnApplicationPreviewed;
            ApplicationLauncher.ApplicationDeactivated += OnApplicationDeactivated;

            // Setup the UI to transform if the UI is rotated.
            // Create a rotation matrix to orient the screen so it is viewed correctly
            // when the user orientation is 180 degress different.
            inverted = Matrix.CreateRotationZ(MathHelper.ToRadians(180)) *
                       Matrix.CreateTranslation(graphics.GraphicsDevice.Viewport.Width,
                                                 graphics.GraphicsDevice.Viewport.Height,
                                                 0);

            if (currentOrientation == UserOrientation.Top)
            {
                screenTransform = inverted;
            }

            base.Initialize();
        }

        /// <summary>
        /// Load your graphics content.
        /// </summary>
        protected override void LoadContent()
        {
            surfaceHeight = Window.ClientBounds.Height;
            surfaceWidth = Window.ClientBounds.Width;
            battleSprite = new SpriteBatch(GraphicsDevice);
            node = Texture2D.FromFile(graphics.GraphicsDevice, @"Resources\planet1.png");
            courierNew = Content.Load<SpriteFont>(@"Resources\Courier New");
            sizeOfNodeHitbox = node.Height;


            nodes.Add(new Node(new Vector2(50, 384), Color.Blue,10));
            nodes.Add(new Node(new Vector2(974, 384), Color.Red,10));

            NodeSpawner(nrOfNodesThatShouldSpawn);

            //graphics.GraphicsDevice.RenderState.PointSize = 5f;

            basicEffect = new BasicEffect(graphics.GraphicsDevice,new EffectPool());

            basicEffect.VertexColorEnabled = true;
            basicEffect.World = Matrix.Identity;
            basicEffect.Projection = Matrix.CreateOrthographicOffCenter(0, surfaceWidth, surfaceHeight, 0, 0, 1);

        }

        /// <summary>
        /// Unload your graphics content.
        /// </summary>
        protected override void UnloadContent()
        {
            Content.Unload();
        }

        /// <summary>
        /// Allows the app to run logic such as updating the world,
        /// checking for collisions, gathering input and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            if (isApplicationActivated || isApplicationPreviewed)
            {
                if (isApplicationActivated)
                {
                    // TODO: Process contacts, 
                    // use the following code to get the state of all current contacts.
                    // ReadOnlyContactCollection contacts = contactTarget.GetState();
                }
                if (gameTime.TotalGameTime.TotalMilliseconds > nextTropSpawn)
                {
                    nextTropSpawn += tropSpawnInterval;
                    foreach (Node updateNode in nodes)
                    {
                        UpdateTroops(updateNode);
                    }
                }

                ReadOnlyContactCollection contacts = contactTarget.GetState();
                foreach (Contact cont in contacts)
                {
                    if (!oldContacts.Contains(cont.Id))
                    {
                        oldContactsStartStop.Add(cont.Id, new Line(new Vector2(cont.CenterX, cont.CenterY), new Vector2(cont.CenterX, cont.CenterY)));
                        oldContacts.Add(cont.Id);
                    }
                    else
                    {
                        oldContactsStartStop[cont.Id].Stop = new Vector2(cont.CenterX, cont.CenterY);
                    }
                }
                List<int> tmpOldCont = new List<int>();
                foreach (int oldCont in oldContacts)
                {
                    if (!contacts.Contains(oldCont))
                    {
                        Node startNode = nodes.Find(delegate(Node n)
                        {
                            return OnTarget(n.Position, oldContactsStartStop[oldCont].Start);
                        });
                        if (startNode != null && startNode.Color != Color.White)
                        {
                            Node stopNode = nodes.Find(delegate(Node n)
                            {
                                return OnTarget(n.Position, oldContactsStartStop[oldCont].Stop);
                            });
                            if (stopNode != null && startNode != stopNode)
                            {
                                if (nodeConnections.ContainsKey(startNode))
                                {
                                    nodeConnections[startNode].Add(stopNode);
                                }
                                else
                                {
                                    nodeConnections.Add(startNode, new List<Node>());
                                    nodeConnections[startNode].Add(stopNode);
                                }
                            }
                        }
                        else
                        {
                            foreach (Node connNode in nodes)
                            {
                                CheckForCrossedConnections(oldContactsStartStop[oldCont], connNode);
                            }
                        }
                        /*foreach (Node startNode in nodes)
                        {
                            if (tmpNode != null && startNode.Color != Color.White && OnTarget(startNode.Position, oldContactsStartStop[oldCont].Start))
                            {
                                foreach (Node stopNode in nodes)
                                {
                                    if (startNode != stopNode && OnTarget(stopNode.Position, oldContactsStartStop[oldCont].Stop))
                                    {
                                        if (nodeConnections.ContainsKey(startNode))
                                        {
                                            nodeConnections[startNode].Add(stopNode);
                                        }
                                        else
                                        {
                                            nodeConnections.Add(startNode, new List<Node>());
                                            nodeConnections[startNode].Add(stopNode);
                                        }
                                    }
                                }//foreach (Node stopNode in nodes)
                                break;
                            }//if (startNode.Color != Color.White && OnTarget(startNode.Position, oldContactsStartStop[oldCont].Start))
                            else
                            {
                                CheckForCrossedConnections(oldContactsStartStop[oldCont],startNode);
                            }
                        }//foreach (Node startNode in nodes)*/

                    }//if (!contacts.Contains(oldCont))
                    else
                    {
                        tmpOldCont.Add(oldCont);
                    }
                    
                }//foreach oldContacts

                oldContacts = tmpOldCont;

            }// if activated
            else
            {
                Exit();
            }

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the app should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            if (!applicationLoadCompleteSignalled)
            {
                // Dismiss the loading screen now that we are starting to draw
                ApplicationLauncher.SignalApplicationLoadComplete();
                applicationLoadCompleteSignalled = true;
            }

            //TODO: Rotate the UI based on the value of screenTransform here if desired

            graphics.GraphicsDevice.Clear(backgroundColor);

            battleSprite.Begin();

            foreach (Node drawNode in nodes)
            {
                float scale = 0.5f + (float)drawNode.NrOfTroops / 35f;
                battleSprite.Draw(node, drawNode.Position, null, drawNode.Color, 0f, new Vector2(node.Width / 2, node.Height / 2), scale, SpriteEffects.None, 1f);
                String tmpString = "" + drawNode.NrOfTroops;
                //battleSprite.DrawString(courierNew, tmpString, drawNode.Position, Color.White, 0, courierNew.MeasureString(tmpString) / 2, 1f, SpriteEffects.None, 1f);

                if (nodeConnections.ContainsKey(drawNode) && nodeConnections[drawNode].Count > 0)
                {
                    foreach (Node lineNode in nodeConnections[drawNode])
                    {
                        lines.Add(new VertexPositionColor(new Vector3(drawNode.Position, 0), drawNode.Color));
                        lines.Add(new VertexPositionColor(new Vector3(lineNode.Position, 0), drawNode.Color));
                    }
                }
            }
            battleSprite.End();

            VertexPositionColor[] lineArray = lines.ToArray();
            lines.Clear();
            if (lineArray.Length > 0)
            {
                basicEffect.Begin();
                foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
                {
                    pass.Begin();
                    
                    graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.LineList, lineArray, 0, lineArray.Length / 2);
                    pass.End();
                }
                basicEffect.End();
            }


            base.Draw(gameTime);
        }

        #endregion

        #region Application Event Handlers

        /// <summary>
        /// This is called when application has been activated.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnApplicationActivated(object sender, EventArgs e)
        {
            // Update application state.
            isApplicationActivated = true;
            isApplicationPreviewed = false;

            // Orientaton can change between activations.
            ResetOrientation();

            //TODO: Enable audio, animations here

            //TODO: Optionally enable raw image here
        }

        /// <summary>
        /// This is called when application is in preview mode.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnApplicationPreviewed(object sender, EventArgs e)
        {
            // Update application state.
            isApplicationActivated = false;
            isApplicationPreviewed = true;

            //TODO: Disable audio here if it is enabled

            //TODO: Optionally enable animations here
        }

        /// <summary>
        ///  This is called when application has been deactivated.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnApplicationDeactivated(object sender, EventArgs e)
        {
            // Update application state.
            isApplicationActivated = false;
            isApplicationPreviewed = false;

            //TODO: Disable audio, animations here

            //TODO: Disable raw image if it's enabled
        }

        #endregion

        #region IDisposable

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                // Release managed resources.
                IDisposable graphicsDispose = graphics as IDisposable;
                if (graphicsDispose != null)
                {
                    graphicsDispose.Dispose();
                }
                if (contactTarget != null)
                {
                    contactTarget.Dispose();
                    contactTarget = null;
                }
            }

            // Release unmanaged Resources.

            // Set large objects to null to facilitate garbage collection.

            base.Dispose(disposing);
        }


        #endregion

    }
}
